﻿using System;
using System.Collections.Generic;
using System.Linq;

using System.Collections;
using System.Reflection;

namespace ElfDoc
{

    public static class ElfDocHelper
    {

        public static object GetObjectFromObjectDictionary(this Dictionary<string, object> d, string key)
        {
            object obj = null;

            //-Code

            if (key.Contains("[") && key.Contains("]"))
            {
                object val = null;

                // get nth object property 
                string headMinusIndexer = key.Substring(0, key.IndexOf("["));
                string indexString = key.Between("[", "]");
                if (!String.IsNullOrEmpty(indexString))
                {
                    int index = Convert.ToInt32(indexString);
                    object oArray = d[headMinusIndexer];
                    IList ival = (IList)oArray;
                    val = ival[index];
                }
                else
                {
                    val = null;
                }
                return val;
            }
            else
            {
                // get object property 
                if (d.ContainsKey(key))
                {
                    obj = d[key];
                }
            }

            //-Return
            return obj;

        }

        public static ValueByKeyTuple ValueByKeyTuple<T>(this T o, string key)
        {

            Dictionary<string, int> dottedContext = new Dictionary<string, int>();

            dynamic dynamicObject = o;
            try
            {
                Dictionary<string, int> dc = dynamicObject.DottedContext;
                dottedContext = dc;
            }
            catch
            {
            }

            //-Return
            return o.ValueByKeyTuple(key, dottedContext);

        }

        public static string ObjectByKeyValueKeyMapper(this string key, Dictionary<string, int> dottedContext)
        {

            //-Declaration

            // the string that signifies a dotted context
            string dottedIndexerString = "[.]";

            //-Code

            // if we have non zero key 
            // and dottedContext array then, we may map the key to something else
            // and the indexer is > 0, 
            // then we map the key to something else
            if (!String.IsNullOrEmpty(key))
            {

                if (dottedContext != null)
                {

                    // does the key contain [.] dotted context ? if yes swap the [.] for indexer number ...
                    if (key.Contains(dottedIndexerString))
                    {

                        // get the string before, ... and search DottedContext for 
                        string keyBeforeDottedContext = key.Substring(0, key.IndexOf(dottedIndexerString));

                        // get the indexer as an int
                        int indexer = dottedContext[keyBeforeDottedContext];

                        // get the indexer as an string
                        if (indexer > 0)
                        {
                            // the new key value ...
                            key = key.Replace(keyBeforeDottedContext + dottedIndexerString, keyBeforeDottedContext + "[" + indexer.ToString() + "]");
                        }

                    }

                }

            }

            //-Return
            return key;

        }

        public static ValueByKeyTuple ValueByKeyTuple<T>(this T o, string key, Dictionary<string, int> dottedContext)
        {

            //-Declaration

            bool valueObtained = false;
            object objectValue = null;

            //-Code

            try
            {

                // the key may need to be mapped to something else 
                // e.g. "CaseConductor.CaseNoteList[.].NoteText" -> "CaseConductor.CaseNoteList[1].NoteText" dottedContext has Add("CaseConductor.CaseNoteList", 1);
                key = key.ObjectByKeyValueKeyMapper(dottedContext);

                // try to get the object value
                objectValue = o.ValueByKey(key);

                // did we get non null ?
                valueObtained = (objectValue == null) ? false : true;

            }
            catch (Exception ex)
            {
                valueObtained = false;
                string msgEx = ex.Message;
            }

            // the value of the property - stringified ; 
            string objectStringValue = (objectValue != null) ? objectValue.ToString() : "";

            // the string-name of the type of the property
            string objectTypeName = (objectValue != null) ? objectValue.GetType().FullName : "";

            // construct a tuple to go back to the caller
            var t = new ValueByKeyTuple
            {
                StringValue = objectStringValue,
                ObjectValue = objectValue,
                TypeName = objectTypeName
                ,
                ValueObtained = valueObtained
            };

            //-Return
            return t;

        }

        public static object ValueByKey<T>(this T o, string key)
        {

            if (!String.IsNullOrEmpty(key))
            {
                if (!key.Contains("."))
                {
                    // no dot in the key, that means we are at the end of the chain of composed objects
                    var d = o.PropertyObjectDictionary();

                    object ovalue = d.GetObjectFromObjectDictionary(key);

                    if (ovalue == null && key.Contains("("))
                    {
                        ovalue = o.CallMethodGetReturn(key);
                    }

                    //-Return
                    return ovalue;

                }
                else
                {
                    // key contains a dot ; therefore get object by the name of the head 
                    // and pass on that object and get property by the tail
                    var d = o.PropertyObjectDictionary();
                    var head = key.Head();
                    if (head.Contains("[") && head.Contains("]"))
                    {

                        object onext = d.GetObjectFromObjectDictionary(head);

                        //-Return
                        return onext.ValueByKey(key.Tail());

                    }
                    else
                    {

                        if (head.Contains("(") && head.Contains(")"))
                        {
                            object onext = o.CallMethodGetReturn(head);

                            //-Return
                            return onext.ValueByKey(key.Tail());
                        }
                        else
                        {
                            // get object property 
                            if (d.ContainsKey(head))
                            {
                                var onext = d[head];

                                //-Return
                                return onext.ValueByKey(key.Tail());
                            }
                            else
                            {
                                return null;
                            }
                        }
                    }
                }
            }

            //-Return
            return null;

        }

        public static Dictionary<string, object> PropertyObjectDictionary(this object o)
        {
            Dictionary<string, object> d = new Dictionary<string, object>();
            if (o != null)
            {
                try
                {
                    d = o.GetType().GetProperties().ToDictionary(p => p.Name, p => p.GetValue(o, null));
                }
                finally
                {
                }
            }
            else
            {
            }

            //-Return
            return d;
        }

        public static string Head(this string key)
        {
            var head = String.Empty;
            var splittBy = '.';
            if (!String.IsNullOrEmpty(key))
            {
                var keyArray = key.Split(splittBy);
                head = keyArray[0];
            }
            //-Return
            return head;
        }

        public static string Tail(this string key)
        {
            var tail = "";
            var splittBy = '.';
            if (!String.IsNullOrEmpty(key))
            {
                var keyArray = key.Split(splittBy);
                for (int i = 1; i < keyArray.Length; i++)
                {
                    tail += (i > 1) ? "." + keyArray[i] : keyArray[i];
                }
            }
            //-Return
            return tail;
        }

        public static string TailLast(this string key)
        {
            var tail = "";
            var splittBy = '.';
            if (!String.IsNullOrEmpty(key))
            {
                var keyArray = key.Split(splittBy);
                switch (keyArray.Length)
                {
                    case 0:
                        tail = keyArray[keyArray.Length - 1];
                        break;
                    default:
                        tail = keyArray[keyArray.Length - 1];
                        break;
                }
            }

            //-Return
            return tail;
        }

        public static string TailLastAfter(this string key, string tailChars)
        {
            var tail = "";
            if (!String.IsNullOrEmpty(key))
            {
                var tailCharsPos = key.IndexOf(tailChars) + tailChars.Length;
                tail = key.Substring(tailCharsPos);
            }

            //-Return
            return tail;
        }

        public static string Between(this string str, string start, string end)
        {
            string between = String.Empty;
            if
            (
                !String.IsNullOrEmpty(str)
                 && !String.IsNullOrEmpty(start)
                 && !String.IsNullOrEmpty(end)
            )
            {
                int from = str.IndexOf(start);
                int to = str.IndexOf(end);
                int len = to - from - 1;
                if (len >= 0)
                {
                    between = str.Substring(from + 1, len);
                }
            }
            return between;
        }

        public static object CallMethodGetReturn(this object obj, string methodCallSig)
        {

            //-Declaration

            // return value of this function
            object ret = null;

            // the parameters to the method that will be invoked
            var parmString = String.Empty;

            // the method name
            string methodName = String.Empty;

            // the args the will passed to the method that will be invoked
            object[] args = null;

            // the method object about the method that will be invoked
            MethodInfo methodInfo = null;

            //-Code

            // the type info of the thing that has the method to be called
            Type t = obj.GetType();

            // which is it ?  the caller may have sent Foo() or Foo(1,"smith", ...) 
            if (methodCallSig.Contains("(") && methodCallSig.Contains(")"))
            {

                // extract the method name from e.g. get "Foo" from the text Foo(1,2,3)
                methodName = methodCallSig.Substring(0, methodCallSig.IndexOf("("));

                // extract the parm text e.g. get "1,2,3" from tge text Foo(1,2,3)
                parmString = methodCallSig.Substring(methodCallSig.IndexOf("(") + 1, methodCallSig.IndexOf(")") - methodCallSig.IndexOf("(") - 1);

            }
            else
            {

                // extract the method name e.g. get "Foo" from the text Foo(1,2,3)
                methodName = methodCallSig;

            }

            // which is it ?  the caller may have sent Foo() or Foo(1,"smith", ...) 
            if (!String.IsNullOrEmpty(parmString))
            {

                // The caller is asking for parms to be sent the method call.

                // split to array
                var parmArray = parmString.Split(',');
                Type[] paramTypes = parmArray.Select(p => p.GetType()).ToArray();

                // get a method info that has the same number as parms as the caller is assuming ...

                // now get the method object for the method name we are interested in
                methodInfo = t.GetMethod(methodName, paramTypes);
                if (methodInfo == null)
                {
                    methodInfo = t.GetMethod(methodName);
                }

                // now try to map the args
                ParameterInfo[] parameterInfoList = methodInfo.GetParameters();
                List<object> argsList = new List<object>(parameterInfoList.Count());
                for (int i = 0; i < parameterInfoList.Count(); i++)
                {
                    argsList.Add(null);
                    var objValue = parmArray[i];
                    var parmTypeString = parameterInfoList[i].ParameterType.ToString();

                    // handle the case where the type is inherited from enum, is any kind of enum 
                    var isEnum = (parameterInfoList[i].ParameterType).IsSubclassOf(typeof(System.Enum));
                    if (isEnum)
                    {
                        parmTypeString = "System.Enum";
                    }

                    switch (parmTypeString)
                    {
                        case "System.String":
                            argsList[i] = (object)(parmArray[i].ToString().Replace("\"", ""));
                            break;
                        case "System.Boolean":
                            argsList[i] = (object)(Convert.ToBoolean(parmArray[i]));
                            break;
                        case "System.Int64":
                            argsList[i] = (object)(Convert.ToInt64(parmArray[i]));
                            break;
                        case "System.Single":
                            argsList[i] = (object)(Convert.ToSingle(parmArray[i]));
                            break;
                        case "System.Double":
                            argsList[i] = (object)(Convert.ToDouble(parmArray[i]));
                            break;
                        case "System.Int32":
                            argsList[i] = (object)(Convert.ToInt32(parmArray[i]));
                            break;
                        case "System.Enum":
                            argsList[i] = Enum.ToObject(parameterInfoList[i].ParameterType, System.Convert.ToInt32(objValue));
                            break;
                            //case "System.DateTime":
                            // need some cleverness here to convert date-string into datetime e.g. "2011 01 02" -> new DateTime1,1,2011)
                            //argsList[i] = (object)(new DateTime((string)parmArray[i]) );
                            break;
                        default:
                            break;
                    }
                }
                args = argsList.ToArray();


            }
            else
            {

                // now get the method object for the method name we are interested in
                methodInfo = t.GetMethod(methodName);
            }

            // now call the method
            if (methodInfo != null)
            {
                ret = t.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, args);
            }

            //-Return
            return ret;

        }


    }

}
